/*
 * 描          述:  <描述>
 * 修  改   人:  Administrator
 * 修改时间:  2020年2月28日
 * <修改描述:>
 */
package com.tx.component.file.resource.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.BucketInfo;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import com.tx.component.file.exception.FileContextResourceException;
import com.tx.component.file.resource.AbstractFCResource;
import com.tx.component.file.resource.FCResource;
import com.tx.component.file.resource.FCResourceFilter;
import com.tx.component.file.resource.FCResourceLoader;
import com.tx.component.file.resource.FCResourceTypeEnum;
import com.tx.component.file.util.FileContextUtils;
import com.tx.core.exceptions.util.AssertUtils;
import com.tx.core.util.MessageUtils;

/**
 * 阿里SSO文件资源加载器<br/>
 * https://help.aliyun.com/document_detail/32008.html?spm=a2c4g.11186623.6.762.61fd2841I5HX6g
 * https://help.aliyun.com/document_detail/32010.html?spm=a2c4g.11186623.6.765.5d2a6328lnIe1R
 * 初始化：             https://help.aliyun.com/document_detail/32010.html?spm=a2c4g.11186623.4.6.369ec06dImMkZE
 * 快速入门:    https://help.aliyun.com/document_detail/32011.html?spm=a2c4g.11186623.4.3.369ec06dImMkZE
 * 
 * @author  Administrator
 * @version  [版本号, 2020年2月28日]
 * @see  [相关类/方法]
 * @since  [产品/模块版本]
 */
public class AliyunOSSFCResourceLoader
        implements FCResourceLoader, InitializingBean {
    
    /** 日志记录句柄 */
    protected static Logger logger = LoggerFactory
            .getLogger(FCResourceLoader.class);
    
    /** 文件夹目录 */
    private String catalog;
    
    /** ossClient */
    private OSS ossClient;
    
    /** bucketName */
    private String bucketName;
    
    /** <默认构造函数> */
    public AliyunOSSFCResourceLoader() {
        super();
    }
    
    /** <默认构造函数> */
    public AliyunOSSFCResourceLoader(OSS ossClient, String bucketName) {
        super();
        this.ossClient = ossClient;
        this.bucketName = bucketName;
    }
    
    /**
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        AssertUtils.notEmpty(this.ossClient, "ossClient is null.");
        AssertUtils.notEmpty(this.bucketName, "bucketName is empty.");
        
        if (!this.ossClient.doesBucketExist(this.bucketName)) {
            //如果创建过程中抛出异常，理应无法启动
            //默认创建公共毒
            CreateBucketRequest cbRequest = new CreateBucketRequest(
                    this.bucketName);
            //设置为公共读
            cbRequest.setCannedACL(CannedAccessControlList.PublicRead);
            this.ossClient.createBucket(cbRequest);
        }
        
        //如果存在则读取bucket信息，如果已被占用，则此处会抛出异常
        BucketInfo bucketInfo = this.ossClient.getBucketInfo(this.bucketName);
        String location = bucketInfo.getBucket().getLocation();
        logger.info(
                "bucket init success. type: AliyunOSS,bucketName: {}, location:{}",
                this.bucketName,
                location);
    }
    
    /**
     * @param fd
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath) {
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new AliyunOSSFCResource(this.catalog,
                this.ossClient, this.bucketName, relativePath);
        return fileResource;
    }
    
    /**
     * @param relativePath
     * @param type
     * @return
     */
    @Override
    public FCResource getFCResource(String relativePath,
            FCResourceTypeEnum type) {
        AssertUtils.notNull(type, "type is null.");
        
        relativePath = relativePath == null ? "" : relativePath;
        //预处理关联路径
        relativePath = FileContextUtils.handleRelativePath(relativePath);
        
        //创建关联资源
        FCResource fileResource = new AliyunOSSFCResource(this.catalog,
                this.ossClient, this.bucketName, relativePath, type);
        return fileResource;
    }
    
    /**
     * OSS文件资源<br/>
     * <功能详细描述>
     * 
     * @author  Administrator
     * @version  [版本号, 2020年3月11日]
     * @see  [相关类/方法]
     * @since  [产品/模块版本]
     */
    public static class AliyunOSSFCResource extends AbstractFCResource {
        
        /** ossClient */
        private OSS ossClient;
        
        /** bucketName */
        private String bucketName;
        
        /** 文件定义 */
        private String relativePath;
        
        /** <默认构造函数> */
        public AliyunOSSFCResource(String catalog, OSS ossClient,
                String bucketName, String relativePath) {
            super(catalog);
            AssertUtils.notNull(ossClient, "ossClient is null.");
            AssertUtils.notEmpty(bucketName, "bucketName is empty.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            //处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            this.relativePath = relativePath;
            this.bucketName = bucketName;
            this.ossClient = ossClient;
            
            setType(FCResourceTypeEnum.FILE);
        }
        
        /** <默认构造函数> */
        public AliyunOSSFCResource(String catalog, OSS ossClient,
                String bucketName, String relativePath,
                FCResourceTypeEnum type) {
            super(catalog);
            AssertUtils.notNull(ossClient, "ossClient is null.");
            AssertUtils.notEmpty(bucketName, "bucketName is empty.");
            AssertUtils.notEmpty(relativePath, "relativePath is empty.");
            
            //处理相对路径
            relativePath = FileContextUtils.handleRelativePath(relativePath);
            
            this.relativePath = relativePath;
            this.bucketName = bucketName;
            this.ossClient = ossClient;
            
            setType(type);
        }
        
        /**
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public FCResourceTypeEnum getType()
                throws FileContextResourceException {
            return this.type;
        }
        
        /**
         * @return 返回 relativePath
         */
        @Override
        public String getRelativePath() {
            return relativePath;
        }
        
        /**
         * @param relativePath
         * @return
         * @throws IOException
         */
        @Override
        public FCResource createRelative(String relativePath) {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    relativePath);
            
            //组装实际资源对象
            FCResource resource = new AliyunOSSFCResource(this.getCatalog(),
                    this.ossClient, this.bucketName, pathToUse);
            return resource;
        }
        
        /**
         * @param name
         * @return
         */
        @Override
        public FCResource getChild(String name) {
            //拼接相对路径
            String pathToUse = StringUtils
                    .applyRelativePath(getRelativePath() + "/", name);
            
            //组装实际资源对象
            FCResource resource = new AliyunOSSFCResource(this.getCatalog(),
                    this.ossClient, this.bucketName, pathToUse);
            return resource;
        }
        
        /**
         * @return
         */
        @Override
        public FCResource getParent() {
            //拼接相对路径
            String pathToUse = StringUtils.applyRelativePath(getRelativePath(),
                    "");
            
            //组装实际资源对象
            FCResource resource = new AliyunOSSFCResource(this.getCatalog(),
                    this.ossClient, this.bucketName, pathToUse,
                    FCResourceTypeEnum.FOLDER);
            return resource;
        }
        
        /**
         * 文件是否存在<br/>
         * @return
         */
        @Override
        public boolean exists() throws FileContextResourceException {
            boolean isExists = false;
            try {
                if (FCResourceTypeEnum.FILE.equals(this.type)) {
                    isExists = this.ossClient.doesObjectExist(this.bucketName,
                            this.relativePath);
                } else {
                    isExists = true;
                }
            } catch (OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.exists() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return isExists;
        }
        
        /**
         * 获取文件资源大小<br/>
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public long getSize() throws FileContextResourceException {
            long size = -1;
            try {
                size = this.ossClient
                        .getObjectMetadata(bucketName, this.relativePath)
                        .getContentLength();
            } catch (OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.exists() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
            return size;
        }
        
        /**
         * 
         */
        @Override
        public void delete() throws FileContextResourceException {
            if (this.exists()) {
                try {
                    this.ossClient.deleteObject(this.bucketName,
                            this.relativePath);
                } catch (OSSException | ClientException e) {
                    throw new FileContextResourceException(MessageUtils.format(
                            "FCResource.exists() error. catalog:{},path:{}.",
                            getCatalog(),
                            getRelativePath()), e);
                }
            }
        }
        
        /**
         * 
         */
        @Override
        public void deleteQuietly() throws FileContextResourceException {
            if (isFile()) {
                delete();
            } else if (isFolder()) {
                ObjectListing listing = this.ossClient
                        .listObjects(this.bucketName, this.relativePath);
                List<String> keys = new ArrayList<>();
                for (OSSObjectSummary summary : listing.getObjectSummaries()) {
                    keys.add(summary.getKey());
                }
                if (keys.size() > 0) {
                    DeleteObjectsRequest dor = new DeleteObjectsRequest(
                            this.bucketName);
                    dor.setKeys(keys);
                    this.ossClient.deleteObjects(dor);
                }
            }
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public InputStream getInputStream()
                throws FileContextResourceException {
            try {
                OSSObject ossObject = this.ossClient.getObject(this.bucketName,
                        this.relativePath);
                return ossObject.getObjectContent();
            } catch (OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.getInputStream() error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void save(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try {
                this.ossClient.putObject(this.bucketName,
                        this.relativePath,
                        inputStream);
            } catch (OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.save(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @param inputStream
         */
        @Override
        public void add(InputStream inputStream)
                throws FileContextResourceException {
            AssertUtils.notNull(inputStream, "inputStream is null.");
            
            if (this.ossClient.doesObjectExist(this.bucketName,
                    this.relativePath)) {
                throw new FileContextResourceException(
                        "新增资源错误，对应资源已经存在.如需覆盖请调用save方法.");
            }
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try {
                this.ossClient.putObject(this.bucketName,
                        this.relativePath,
                        inputStream);
            } catch (OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.add(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        ///**
        // * @param inputStream
        // * @throws FileContextResourceException
        // */
        //@Override
        //public void append(InputStream inputStream)
        //        throws FileContextResourceException {
        //    AssertUtils.notNull(inputStream, "inputStream is null.");
        //    
        //    //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
        //    try {
        //        AppendObjectRequest appendObjectRequest = new AppendObjectRequest(
        //                this.bucketName, this.relativePath, inputStream);
        //        this.ossClient.appendObject(appendObjectRequest);
        //    } catch (OSSException | ClientException e) {
        //        throw new FileContextResourceException(MessageUtils.format(
        //                "FCResource.add(InputStream) error. catalog:{},path:{}.",
        //                getCatalog(),
        //                getRelativePath()), e);
        //    }
        //}
        
        /**
         * @param fileFilter
         * @param dirFilter
         * @param depthwise
         * @return
         * @throws FileContextResourceException
         */
        @Override
        public List<FCResource> findFiles(FCResourceFilter fileFilter,
                boolean depthwise) throws FileContextResourceException {
            ObjectListing listing = this.ossClient.listObjects(this.bucketName,
                    this.relativePath);
            List<FCResource> resList = new ArrayList<>();
            for (OSSObjectSummary summary : listing.getObjectSummaries()) {
                FCResource res = new AliyunOSSFCResource(getCatalog(),
                        this.ossClient, this.bucketName, summary.getKey());
                if (fileFilter != null) {
                    if (fileFilter.accept(res)) {
                        resList.add(res);
                    }
                } else {
                    resList.add(res);
                }
            }
            return resList;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public List<FCResource> getChildren()
                throws FileContextResourceException {
            String[] relativePathTokens = org.apache.commons.lang3.StringUtils
                    .splitByWholeSeparator(this.relativePath, "/");
            int tokenLength = relativePathTokens.length;
            ObjectListing listing = this.ossClient.listObjects(this.bucketName,
                    this.relativePath);
            List<FCResource> resList = new ArrayList<>();
            
            Set<String> folderNames = new HashSet<>();
            for (OSSObjectSummary summary : listing.getObjectSummaries()) {
                String relativePathTemp = summary.getKey();
                String[] relativePathTokenTemps = org.apache.commons.lang3.StringUtils
                        .splitByWholeSeparator(relativePathTemp, "/");
                if (relativePathTokenTemps.length == tokenLength + 1) {
                    FCResource res = new AliyunOSSFCResource(getCatalog(),
                            this.ossClient, this.bucketName, summary.getKey());
                    resList.add(res);
                } else if (relativePathTokenTemps.length > tokenLength + 1) {
                    folderNames.add(relativePathTokenTemps[tokenLength]);
                }
            }
            
            for (String folderName : folderNames) {
                String pathToUse = StringUtils
                        .applyRelativePath(getRelativePath() + "/", folderName);
                FCResource res = new AliyunOSSFCResource(getCatalog(),
                        this.ossClient, this.bucketName, pathToUse,
                        FCResourceTypeEnum.FOLDER);
                
                resList.add(res);
            }
            return resList;
        }
        
        /**
         * 
         */
        @Override
        public void createFile() throws FileContextResourceException {
            //donothing
            this.type = FCResourceTypeEnum.FILE;
            //其返回结果中Response为空,所以只能依赖oss中抛出的异常来判断
            try (InputStream inputStream = IOUtils.toInputStream("", "UTF-8")) {
                this.ossClient.putObject(this.bucketName,
                        this.relativePath,
                        inputStream);
            } catch (IOException | OSSException | ClientException e) {
                throw new FileContextResourceException(MessageUtils.format(
                        "FCResource.save(InputStream) error. catalog:{},path:{}.",
                        getCatalog(),
                        getRelativePath()), e);
            }
        }
        
        /**
         * @throws IOException
         */
        @Override
        public void createFolder() throws FileContextResourceException {
            this.type = FCResourceTypeEnum.FOLDER;
        }
        
        /**
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream()
                throws FileContextResourceException {
            throw new FileContextResourceException(
                    "unsupport method getOutputStream");
        }
        
        /**
         * @param bAppend
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream getOutputStream(boolean bAppend)
                throws FileContextResourceException {
            throw new FileContextResourceException(
                    "unsupport method getOutputStream");
        }
    }
}
